/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "GameObject.h"

#include "Component.h"
#include "TransformComponent.h"
#include "ScriptComponent.h"
#include "SceneObject.h"

namespace PQEngine
{
	GameObject* GameObject::_root=NULL;
	Ogre::SceneNode* GameObject::_rootSceneNode=NULL;
	GameObject::MapList GameObject::_list=GameObject::MapList();
	GameObject* GameObject::get(Ogre::SceneNode* node)
	{
		MapList::iterator it=_list.find(node);
		if(it!=_list.end()){
			return it->second;
		}
		return 0;
	}

	GameObject* GameObject::getRootObject()
	{
		return _root;
	}

	GameObject::GameObject(SceneObject* sceneObject,GameObjectType type)
		: Object(OBJECT_TYPE_GAME_OBJECT),_sceneObject(sceneObject),_gameObjectType(type),
		_name("GameObject"),_isRoot(false),_isRigidBody(false),_hasCollider(false),_isTerrain(false),
		_sceneNode(0),_selected(false)
	{
		assert(_sceneObject);

		initGameObject(type);

		created();
	}

	void GameObject::initGameObject(GameObjectType type)
	{
		switch(type)
		{
		case GAME_OBJECT_ROOT:
			_isRoot=true;
			_sceneNode=_sceneObject->getRootSceneNode();
			break;
		case GAME_OBJECT_TERRAIN:
			_isTerrain=true;
			break;

		case GAME_OBJECT_EMPTY:
		case GAME_OBJECT_ENTITY:
		case GAME_OBJECT_LIGHT:
		case GAME_OBJECT_CAMERA:
		default:
			initEmptyObject();
			break;
		}
	}

	void GameObject::initEmptyObject()
	{
		_sceneNode=_sceneObject->createChildSceneNode();
		TransformComponent *component=new TransformComponent();
		component->attach(this);
		
		_sceneObject->getRootGameObject()->addChild(this);//default all other object is a child of root
		_list.insert(GameObject::MapList::value_type(_sceneNode,this));
	}

	GameObject::~GameObject()
	{
		int size=_children.size();
		for(int i=0;i<size;i++){
			delete _children[i];
		}

		Ogre::SceneNode *node= _sceneNode;
		if(node){
			Ogre::SceneNode *parentNode=node->getParentSceneNode();
			if(parentNode){
				parentNode->removeAndDestroyAllChildren();//first detach object
			}
		}

		for(int j=0;j<COMPONENT_TYPE_COUNT;j++){
			size=_components[j].size();
			for(int i=0;i<size;i++){
				delete _components[j][i];//then destroy object
			}
		}

	}

	

	float GameObject::getRadius(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			return mesh->getRadius();
		}else{
			return 0.0f;
		}
	}

	Ogre::Vector3 GameObject::getBoundingBoxSize(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			return mesh->getBoundingBoxSize();
		}else{
			return Ogre::Vector3(0,0,0);
		}
	}

	Ogre::AxisAlignedBox GameObject::getBoundingBox(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			return mesh->getBoundingBox();
		}else{
			return Ogre::AxisAlignedBox();
		}
	}

	Ogre::AxisAlignedBox GameObject::getWorldBoundingBox(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			return mesh->getWorldBoundingBox();
		}else{
			return Ogre::AxisAlignedBox::BOX_NULL;
		}
	}

	int GameObject::getChildrenCount()
	{
		return _children.size();
	}

	GameObject* GameObject::getChild(int index)
	{
		int size=_children.size();
		if(index>=0&&index<size){
			return _children[index];
		}
		return NULL;
	}

	Ogre::SceneNode* GameObject::getSceneNode()
	{
		return _sceneNode;
	}

	void GameObject::setPosition(float x,float y,float z)
	{
		if(_sceneNode){
			_sceneNode->setPosition(x,y,z);
		}
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setPosition(x,y,z);
		}
	}

	void GameObject::setPosition(Ogre::Vector3 position)
	{
		if(_sceneNode){
			_sceneNode->setPosition(position);
		}
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setPosition(position);
		}
	}

	void GameObject::setOrientation(Ogre::Quaternion orient)
	{
		if(_sceneNode){
			_sceneNode->setOrientation(orient);
		}
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setOrientation(orient);
		}
	}

	void GameObject::setScale(Ogre::Vector3 scale)
	{
		if(_sceneNode){
			_sceneNode->setScale(scale);
		}
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setScale(scale);
		}
	}

	//recursive update
	void GameObject::update(float deltaT)
	{
		//update game object components

		for(int i=0;i<COMPONENT_TYPE_COUNT;i++){
			ComponentVector::iterator it  =  _components[i].begin();
			ComponentVector::iterator end  =  _components[i].end();
			for(;it!=end;++it){
				(*it)->update(deltaT);
			}
		}
		
		//then update child
		GameObjectVector::iterator objIter  =  _children.begin();
		for(;objIter!=_children.end();++objIter){
			(*objIter)->update(deltaT);
		}
	}

	void GameObject::addComponent(Component *component)
	{
		if(!component){
			return;
		}

		ObjectType type=component->getObjectType();
		switch(type){
			case OBJECT_TYPE_COMPONENT_TRANSFORM:			
				addTransform((TransformComponent*)component);
				return;
			case OBJECT_TYPE_COMPONENT_ENTITY:			
				addEntity((MeshComponent*)component);
				return;
			case OBJECT_TYPE_COMPONENT_LIGHT:			
				addLight((LightComponent*)component);
				return;
			case OBJECT_TYPE_COMPONENT_RIGIDBODY:			
				addRigidBody(component);
				return;
			case OBJECT_TYPE_COMPONENT_PLANE_COLLIDER:
			case OBJECT_TYPE_COMPONENT_BOX_COLLIDER:
			case OBJECT_TYPE_COMPONENT_SPHERE_COLLIDER:
			case OBJECT_TYPE_COMPONENT_CYLINDER_COLLIDER:
			case OBJECT_TYPE_COMPONENT_CONE_COLLIDER:
			case OBJECT_TYPE_COMPONENT_CAPSULE_COLLIDER:
			case OBJECT_TYPE_COMPONENT_CONVEX_HULL_COLLIDER:
				addCollider(component);
				return;
			case OBJECT_TYPE_COMPONENT_SCRIPT:
				addScript(component);
				return;
			case OBJECT_TYPE_COMPONENT_TERRAIN:
				addTerrain(component);
				return;
			default:
				ShouldNotReach("GameObject::addComponent");
				
				return;
		}
	}

	
	void GameObject::addScript(Component* component)
	{
		ScriptComponent* scriptComponent=(ScriptComponent*)component;
		std::string path=scriptComponent->getScriptPath();
		ScriptComponent* result=getScriptComponent(path);
		if(result){
			if(result!=scriptComponent){
				Object::removeObject(component->getId());
			}
			return;
		}
		_components[COMPONENT_TYPE_SCRIPT].push_back(component);
		
	}

	void GameObject::addEntity(MeshComponent* component)
	{
		
		assert(component||_components[COMPONENT_TYPE_MESH].size()>0);

		Ogre::Entity* entity=component->getEntity();
		_sceneNode->attachObject(entity);
		_components[COMPONENT_TYPE_MESH].push_back(component);
		
		_isEntity=true;
	}

	void GameObject::addLight(LightComponent* component)
	{
		assert(component||_components[COMPONENT_TYPE_LIGHT].size()>0);

		Ogre::Light* light=component->getLight();
		_sceneNode->attachObject(light);

		_components[COMPONENT_TYPE_LIGHT].push_back(component);
		_isLight=true;
	}


	void GameObject::addTransform(TransformComponent* component)
	{
		assert(component||_components[COMPONENT_TYPE_TRANSFORM].size()>0);

		setPosition(component->getPosition());
		setOrientation(component->getOrientation());
		setScale(component->getScale());

		_components[COMPONENT_TYPE_TRANSFORM].push_back(component);
		
	}

	void GameObject::addRigidBody(Component* component)
	{
		assert(component||_components[COMPONENT_TYPE_RIGIDBODY].size()>0);

		_components[COMPONENT_TYPE_RIGIDBODY].push_back(component);
		_isRigidBody=true;
	}

	void GameObject::addCollider(Component* component)
	{
		if(component==NULL){//collider is a little special unique component
			return;
		}
		_hasCollider=true;
		int cnt=_components[COMPONENT_TYPE_COLLIDER].size();
		if(cnt==1){
			Component* cpnt=_components[COMPONENT_TYPE_COLLIDER][0];
			_components[COMPONENT_TYPE_COLLIDER][0]=component;

			Object::removeObject(cpnt->getId());
			
		}else if(cnt==0){
			_components[COMPONENT_TYPE_COLLIDER].push_back(component);
		}else{
			ShouldNotReach("GameObject::addCollider");
		}
	}

	void GameObject::addTerrain(Component* component)
	{
		if(component==NULL){//collider is a little special unique component
			return;
		}
		_isTerrain=true;
		int cnt=_components[COMPONENT_TYPE_TERRAIN].size();
		if(cnt==1){
			Component* cpnt=_components[COMPONENT_TYPE_COLLIDER][0];
			_components[COMPONENT_TYPE_TERRAIN][0]=component;

			Object::removeObject(cpnt->getId());
			
		}else if(cnt==0){
			_components[COMPONENT_TYPE_TERRAIN].push_back(component);
		}else{
			ShouldNotReach("GameObject::addTerrain");
		}
	}


	bool GameObject::hasComponent(Component *component)
	{
		if(!component){
			return false;
		}
		ComponentType type=component->getComponentType();
		ComponentVector vec=_components[type];
		int size=vec.size();
		for(int i=0;i<size;i++){
			if(component==vec[i]){
				return true;
			}
		}
		return false;	
	}


	bool GameObject::removeComponent(Component *component)
	{
		if(!component){
			return false;
		}
		ComponentType type=component->getComponentType();
		ComponentVector vec=_components[type];
		int size=vec.size();
		for(int i=0;i<size;i++){
			if(component==vec[i]){
				vec[i]=0;
				Object::removeObject(component->getId());
				return true;
			}
		}

		return false;
		
	}

	void GameObject::addChild(GameObject *object)
	{
		if(object){
			object->setParent(this);
			
			Ogre::SceneNode* sceneNode=object->getSceneNode();
			Ogre::SceneNode* parentNode=sceneNode->getParentSceneNode();
			parentNode->removeChild(sceneNode);
			_sceneNode->addChild(sceneNode);

			GameObject* parent = object->getParent();
			if(parent){
				parent->removeChild(object);
			}

			_children.push_back(object);
			
		}
	}

	void GameObject::removeChild(GameObject *object)
	{
		GameObjectVector::iterator iter = find(_children.begin(), _children.end(), object);
		if(iter!=_children.end()){
			_children.erase(iter);
			
		}
	}

	void GameObject::setParent(GameObject* parent)
	{
		_parent=parent;
	}

	GameObject* GameObject::getParent()
	{
		return _parent;
	}

	int GameObject::getComponentCount()
	{
		int cnt=0;
		for(int i=0;i<COMPONENT_TYPE_COUNT;i++){
			cnt+=_components[i].size();
		}
		return cnt;
	}

	Component* GameObject::getComponent(int index)
	{
		int min=0;
		int max=0;
		for(int i=0;i<COMPONENT_TYPE_COUNT;i++){
			max+=_components[i].size();
			if(index>=min&&index<max){
				return _components[i][index-min];
			}
			min=max;
		}
		return 0;
	}

	ScriptComponent* GameObject::getScriptComponent(std::string scriptPath)
	{
		ComponentVector vec=_components[COMPONENT_TYPE_SCRIPT];
		int size=vec.size();
		for(int i=0;i<size;i++){	
			ScriptComponent* component=(ScriptComponent*)vec[i];
			if(component->getScriptPath()==scriptPath){
				return component;
			}
		}

		return 0;
	}

	
	RigidBodyComponent* GameObject::getRigidBodyComponent()
	{
		ComponentVector vec=_components[COMPONENT_TYPE_RIGIDBODY];
		int size=vec.size();
		if(size==1){
			assert(vec[0]->getComponentType()==COMPONENT_TYPE_RIGIDBODY);
			return (RigidBodyComponent*)vec[0];
		}

		return 0;
	}

	
	Component* GameObject::getColliderComponent()
	{
		ComponentVector vec=_components[COMPONENT_TYPE_COLLIDER];
		int size=vec.size();
		if(size==1){
			
			return vec[0];
		}

		return 0;
		
	}

	MeshComponent* GameObject::getMeshComponent()
	{
		ComponentVector vec=_components[COMPONENT_TYPE_MESH];
		int size=vec.size();
		if(size==1){
			assert(vec[0]->getComponentType()==COMPONENT_TYPE_MESH);
			return (MeshComponent*)vec[0];
		}

		return 0;
	}

	//transform component is special because it share by many sub systems
	TransformComponent* GameObject::getTransformComponent()
	{
		ComponentVector vec=_components[COMPONENT_TYPE_TRANSFORM];
		int size=vec.size();
		if(size==1){
			assert(vec[0]->getComponentType()==COMPONENT_TYPE_TRANSFORM);
			return (TransformComponent*)vec[0];
		}

		return 0;
	}


	void GameObject::selectObject(bool selected)
	{
		_selected=selected;
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			mesh->highLight(selected);
		}

		this->select(selected);
		
	}

	void GameObject::setMaterialName(std::string materialName)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			mesh->setMaterialName(materialName);
		}
		
	}

	void GameObject::setNormalMaterial()
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			mesh->setNormalMaterial();
		}
	}



}
